/**
 * CS340 Fatboy Mafia Team
 */
package fatboy.test;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;

import java.io.PrintStream;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;

import junit.framework.Assert;

import org.junit.Test;

import fatboy.addons.*;
import fatboy.hypeerweb.Connection;
import fatboy.hypeerweb.Node;
import fatboy.hypeerweb.NodeAddress;

/**
 * Black Box testing of SendMessage class and Broadcast with acknowledgment.
 * 
 * In the test below I'm testing the following:
 * 	1) For my Boundary Value testing (with a boundary hypeerweb size of 1 node)
 * 		 I used the following test cases:
 * 		 a. Empty HypeerWeb (zero nodes, just outside the boundary)
 * 			- erroneous condition, expected correct error handling
 * 		 b. Send a message to myself/Broadcast with ACK to self
 * 			 (single node, boundary value)
 * 			- expected correct message delivery
 *		 c. Send a message between 2 nodes back and forward/
 *			 Broadcast with ACK with only 2 nodes(just above the boundary)
 *			- expected correct message delivery
 *		 d. Send a message between many nodes in the HypeerWeb (far above the
 *			 boundary).
 *	2) For Informational Domain Testing I tested the order of execution of the
 *		sendMessage and Broadcast with ACL commands in the HypeerWeb of 2 nodes
 *		and many nodes. Also, for informational domain I tested sendMessage
 *		from different type of node - from the Edge node.
 *	3) For Random testing I send message to a random node/Broadcasted with ACK
 *		from the random node to reveal additional equivalence classes.
 * Input domain: NodeAddress >=0
 * 
 * @author Ilya Vinogradov
 */
public class IlyasBlackBoxTesting {

	/**
	 * Empty hypeerweb - invalid case handling
	 */
	@Test (expected = NullPointerException.class) //Oops! Not handling input correctly.
	public void emptyHypeerWebTest() {
		//make a sample hypeer web
		Connection web = new Connection();
		
		//Creating data - not necessary, it fails during the sending
		//MessageData data = new MessageData(new PrintOut(), web.getMyConnection());
		
		//Sending a message to myself - Passing a NULL root node
		web.sendMessage(web.getMyConnection(), new PrintOut());
	}
	
	/**
	 * testing sendMessage with on a hypeerweb with a single node
	 *  (boundary value case)
	 * @return 
	 */
	@Test
	public void sendMessageToMyselfTest() {
		//Setting up output redirection to a file
		File output = JacksBlackBoxTesting.getOutput();
		
		//System.out.println("===== Sending a message to myself in a hypeerweb with a single node =====");
		
		//make a sample hypeer web
		Connection web = new Connection();
		
		//creating a single node in the hypeerweb
		Node testNode1 = web.addNode("first");
		
		//Creating data
		MessageData data = new MessageData(new PrintOut(), web.getMyConnection());
		
		//testing the data prior to send a message
		Assert.assertFalse(data.getCommand().equals(testNode1));
		Assert.assertTrue(data.getTarget().equals(testNode1));
		
		//Sending a message to myself
		web.sendMessage(testNode1, new PrintOut());
		
		//System.out.println("Expected: " + (generateCorrectOutput(testNode1)));
		verifyOutput(output, generateCorrectOutput(web.getAllNodes()));
		
	}
	
	
	/**
	 * testing sendMessage with a hypeerweb with different numbers of nodes 
	 * Tests informational domain - order of inputs
	 * Tests boundary value being close to the boundary
	 * @return 
	 */
	@Test
	public void sendMessageTwoNodesTest() {
		//Setting up output redirection to a file
		File output = JacksBlackBoxTesting.getOutput();
		
		//System.out.println("\n===== Sending a message in a hypeerweb with two nodes =====");
		//make a sample hypeer web with 2 nodes
		Connection web = JacksBlackBoxTesting.setupWeb(2);
		
		//Creating data
		MessageData data = new MessageData(new PrintOut(), web.getMyConnection());
		
		//testing the data prior to send a message
		Assert.assertFalse(data.getCommand() instanceof SendMessage);
		Assert.assertTrue(data.getTarget().equals(web.getMyConnection()));
		
		//Removal Sequence of nodes
		List<Node> remSeq = new ArrayList<Node>();
		
		//Sending a message to the second node
		web.sendMessage(web.getAllNodes().get(1), new PrintOut());
		remSeq.add(web.getAllNodes().get(1));
		
		//Sending a message to the first node
		web.sendMessage(web.getAllNodes().get(0), new PrintOut());
		remSeq.add(web.getAllNodes().get(0));
		
		verifyOutput(output, generateCorrectOutput(remSeq));
	}
	
	/**
	 * testing send message with a hypeerweb with 3 nodes with origin in the edge node
	 * Tests informational domain - order of inputs
	 */
	@Test
	public void sendMessageFromEdgeTest() {
		//Setting up output redirection to a file
		File output = JacksBlackBoxTesting.getOutput();
		//System.out.println("\n===== Sending a message from the edge node in a hypeerweb with 3 nodes =====");
		
		//make a sample hypeer web with 3 nodes
		Connection web = JacksBlackBoxTesting.setupWeb(3);
		List<Node> nodeList = web.getAllNodes();
		
		//Creating data
		MessageData data = new MessageData(new PrintOut(), nodeList.get(2));
		
		//testing the data prior to send a message
		Assert.assertFalse(data.getCommand() instanceof SendMessage);
		Assert.assertTrue(data.getTarget().equals(nodeList.get(2)));
		
		//Removal Sequence of nodes
		List<Node> remSeq = new ArrayList<Node>();
				
		//Sending a message to the second node
		web.sendMessage(nodeList.get(1), new PrintOut());
		remSeq.add(nodeList.get(1));
		
		//Sending a message to the first node
		web.sendMessage(nodeList.get(0), new PrintOut());
		remSeq.add(nodeList.get(0));
		
		//Sending a message to the third node
		web.sendMessage(nodeList.get(2), new PrintOut());
		remSeq.add(nodeList.get(2));
		
		verifyOutput(output, generateCorrectOutput(remSeq));
	}
	
	/**
	 * testing send message with a hypeerweb with many nodes with origin in node 0.
	 * Tests informational domain - order of inputs (message sent to from the middle 
	 * of the hypeerweb to the end, and then from the middle to the beginning)
	 */
	@Test
	public void sendMessageToManyNodes() {
		//Setting up output redirection to a file
		File output = JacksBlackBoxTesting.getOutput();
		//System.out.println("\n===== Sending a message in a hypeerweb with many nodes =====");
		
		//How many nodes to create
		int NUMBER_OF_NODES = 33;
		
		//make a sample hypeer web
		Connection web = JacksBlackBoxTesting.setupWeb(NUMBER_OF_NODES);
		
		//Creating data
		MessageData data = new MessageData(new PrintOut(), web.getMyConnection());
		
		//testing the data prior to send a message
		Assert.assertFalse(data.getCommand() instanceof SendMessage);
		Assert.assertTrue(data.getTarget().equals(web.getAllNodes().get(0)));
		
		//Removal Sequence of nodes
		List<Node> remSeq = new ArrayList<Node>();
		
		//sending the middle of the hypeerweb to the end
		for (int i = NUMBER_OF_NODES/2; i < NUMBER_OF_NODES; i++) {
			web.sendMessage(web.getAllNodes().get(i), new PrintOut());
			remSeq.add(web.getAllNodes().get(i));
		}
		
		//sending the middle of the hypeerweb to the begining
		for (int i = NUMBER_OF_NODES/2; i >=0; i--) {
			web.sendMessage(web.getAllNodes().get(i), new PrintOut());
			remSeq.add(web.getAllNodes().get(i));
		}
		verifyOutput(output, generateCorrectOutput(remSeq));
	}
	
	/**
	 * Testing Send Message to a random node
	 * Tests informational domain - order of inputs
	 * Random testing
	 */
	@Test
	public void sendMessageToARandomNode() {
		//Setting up output redirection to a file
		File output = JacksBlackBoxTesting.getOutput();
		//System.out.println("\n===== Sending a message to a random node in a hypeerweb with many nodes =====");
		
		//How many nodes to create
		int NUMBER_OF_NODES = 33;
		
		//make a sample hypeer web
		Connection web = JacksBlackBoxTesting.setupWeb(NUMBER_OF_NODES);
		
		//Creating data
		MessageData data = new MessageData(new PrintOut(), web.getMyConnection());
		
		//testing the data prior to send a message
		Assert.assertFalse(data.getCommand() instanceof SendMessage);
		Assert.assertTrue(data.getTarget().equals(web.getAllNodes().get(0)));
		
		//Removal Sequence of nodes
		List<Node> remSeq = new ArrayList<Node>();
		
		//sending message to the random node in the hypeerweb
		Random random = new Random();
		for (int i = 0; i < NUMBER_OF_NODES; i++) {
			Node remNode = web.getAllNodes().get(random.nextInt(NUMBER_OF_NODES));
			web.sendMessage(remNode, new PrintOut());
			remSeq.add(remNode);
		}
		
		verifyOutput(output, generateCorrectOutput(remSeq));
	}
	
	/**
	 * Routine that generates expected output for send message
	 */
	private File  generateCorrectOutput(List<Node> nodeList) {
		PrintStream ps = null;
		File output = new File("expected.txt");
		try {
			ps = new PrintStream(output);
		} catch (FileNotFoundException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		for(Node node : nodeList) {
			//if node address is not zero
			if(NodeAddress.asInt(node.getAddress()) != 0) {
				ps.println("Sending message from 0 (0) to " + node.getAddress() );
			}
			ps.println("Executing message at " + node.getAddress());
			ps.println("Message successfully sent to: " + node.getAddress());
		}
		ps.close();
		return output;
	}
	
	/**
	 * Routine that compares two files with actual and expected output
	 */
	private void verifyOutput(File actual, File expected) {
		FileReader inputActual = null;
		FileReader inputExpected = null;
		
		try {
			int act, exp;
			inputActual = new FileReader(actual);
			inputExpected = new FileReader(expected);
			while (((act = inputActual.read()) != -1) && ((exp = inputExpected.read()) != -1)) {
                Assert.assertEquals(exp, act);
            }
			inputActual.close();
			inputExpected.close();
		} catch (FileNotFoundException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
	
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		
	}
	
	/**
	 * Broadcast with acknowledgment test,
	 * Sending broadcast in the empty hypeerweb
	 * Invalid input test
	 * Boundary value test
	 */
	@Test (expected = IllegalArgumentException.class) //Good handling!
	public void BroadcastWithAckEmptyTest() {
		//make a sample hypeer web
		Connection web = new Connection();
		
		web.broadcastWithAck(web.getMyConnection());
	}
	
	/**
	 * Broadcast with acknowledgment test,
	 * Sending broadcast in the hypeerweb with 1 node
	 * Testing boundary value
	 */
	@Test
	public void BroadcastWithAckOneNodeTest() {
		//Setting up output redirection to a file
		File output = JacksBlackBoxTesting.getOutput();
		
		//How many nodes to create
		int NUMBER_OF_NODES = 1;
		
		//make a sample hypeer web
		Connection web = JacksBlackBoxTesting.setupWeb(NUMBER_OF_NODES);
		
		web.broadcastWithAck(web.getAllNodes().get(0));
		testBroadcastWithAck.verifyOutput(output);
	}
	
	/**
	 * Broadcast with acknowledgment test,
	 * Sending broadcast in the hypeerweb with 2 nodes, broadcasting from root
	 * Testing boundary value
	 * Tests informational domain - order of inputs
	 */
	@Test
	public void BroadcastWithAckTwoNodeTest1() {
		//Setting up output redirection to a file
		File output = JacksBlackBoxTesting.getOutput();
		
		//How many nodes to create
		int NUMBER_OF_NODES = 2;
		
		//make a sample hypeer web
		Connection web = JacksBlackBoxTesting.setupWeb(NUMBER_OF_NODES);
		
		web.broadcastWithAck(web.getAllNodes().get(0));
		testBroadcastWithAck.verifyOutput(output);
	}
	
	/**
	 * Broadcast with acknowledgment test,
	 * Sending broadcast in the hypeerweb with 2 nodes, broadcasting from second node
	 * Testing boundary value
	 * Tests informational domain - order of inputs
	 */
	@Test
	public void BroadcastWithAckTwoNodeTest2() {
		//Setting up output redirection to a file
		File output = JacksBlackBoxTesting.getOutput();
		
		//How many nodes to create
		int NUMBER_OF_NODES = 2;
		
		//make a sample hypeer web
		Connection web = JacksBlackBoxTesting.setupWeb(NUMBER_OF_NODES);
		
		web.broadcastWithAck(web.getAllNodes().get(1));
		testBroadcastWithAck.verifyOutput(output);
	}
	
	/**
	 * Broadcast with acknowledgment test,
	 * Sending broadcast in the hypeerweb with 3 nodes, broadcasting from the root
	 * Tests informational domain - order of inputs
	 */
	@Test
	public void BroadcastWithAckThreeNodeTest1() {
		//Setting up output redirection to a file
		File output = JacksBlackBoxTesting.getOutput();
		
		//How many nodes to create
		int NUMBER_OF_NODES = 3;
		
		//make a sample hypeer web
		Connection web = JacksBlackBoxTesting.setupWeb(NUMBER_OF_NODES);
		
		web.broadcastWithAck(web.getAllNodes().get(0));
		testBroadcastWithAck.verifyOutput(output);
	}
	
	/**
	 * Broadcast with acknowledgment test,
	 * Sending broadcast in the hypeerweb with 3 nodes, broadcasting from second node
	 * Tests informational domain - order of inputs
	 */
	@Test
	public void BroadcastWithAckThreeNodeTest2() {
		//Setting up output redirection to a file
		File output = JacksBlackBoxTesting.getOutput();
		
		//How many nodes to create
		int NUMBER_OF_NODES = 3;
		
		//make a sample hypeer web
		Connection web = JacksBlackBoxTesting.setupWeb(NUMBER_OF_NODES);
		
		web.broadcastWithAck(web.getAllNodes().get(1));
		testBroadcastWithAck.verifyOutput(output);
	}
	
	/**
	 * Broadcast with acknowledgment test,
	 * Sending broadcast in the hypeerweb with 3 nodes, broadcasting from third node
	 * Tests informational domain - order of inputs
	 */
	@Test
	public void BroadcastWithAckThreeNodeTest3() {
		//Setting up output redirection to a file
		File output = JacksBlackBoxTesting.getOutput();
		
		//How many nodes to create
		int NUMBER_OF_NODES = 3;
		
		//make a sample hypeer web
		Connection web = JacksBlackBoxTesting.setupWeb(NUMBER_OF_NODES);
		
		web.broadcastWithAck(web.getAllNodes().get(2));
		testBroadcastWithAck.verifyOutput(output);
	}
	
	/**
	 * Broadcast with acknowledgment test,
	 * Sending broadcast from the node in the middle of the hypeerweb
	 * Tests informational domain - order of inputs
	 */
	@Test
	public void BroadcastWithAckMiddleTest() {
		//Setting up output redirection to a file
		File output = JacksBlackBoxTesting.getOutput();
		
		//How many nodes to create
		int NUMBER_OF_NODES = 33;
		
		//make a sample hypeer web
		Connection web = JacksBlackBoxTesting.setupWeb(NUMBER_OF_NODES);
		
		web.broadcastWithAck(web.getAllNodes().get(NUMBER_OF_NODES/2));
		testBroadcastWithAck.verifyOutput(output);
	}
	
	/**
	 * Broadcast with acknowledgment test,
	 * Sending broadcast from a random node in the hypeerweb
	 * Tests informational domain - order of inputs
	 * Random test
	 */
	@Test
	public void BroadcastWithAckRandomTest() {
		// Randomization
		Random rand = new Random();
		
		//run a 100 test cases
		for(int i = 0; i < 100; i ++) {
			//Setting up output redirection to a file
			File output = JacksBlackBoxTesting.getOutput();
			
			//How many nodes to create - (0..100], so at least 1, at most 100
			int NUMBER_OF_NODES = rand.nextInt(100) + 1;
			
			//make a sample hypeer web
			Connection web = JacksBlackBoxTesting.setupWeb(NUMBER_OF_NODES);
			
			//choosing an arbitrary node to initiate a broadcast with acknowledgment
			web.broadcastWithAck(web.getAllNodes().get(rand.nextInt(NUMBER_OF_NODES)));
			testBroadcastWithAck.verifyOutput(output);
		}
	}
}
